#include "sdk.h"
#include "M_Code_StyledTextCtrl.h"
#include <wx/url.h>
#include <wx/encconv.h>
#include <memory>

template<> FileManager* Mgr<FileManager>::instance = nullptr;
template<> bool  Mgr<FileManager>::isShutdown = false;

LoaderBase::~LoaderBase() {
  WaitReady();
  delete[] data;
}

bool LoaderBase::Sync() {
  WaitReady();
  return data;
}

char* LoaderBase::GetData() {
  WaitReady();
  return data;
}

size_t LoaderBase::GetLength() {
  WaitReady();
  return len;
}

void FileLoader::operator()() {
  if( !wxFile::Access( fileName, wxFile::read ) || wxFileName::DirExists( fileName ) ) {
    Ready();
    return;
  }
  wxFile file( fileName );
  len = file.Length();
  data = new char[len + 4];
  char *dp = data + len;
  *dp++ = '\0';
  *dp++ = '\0';
  *dp++ = '\0';
  *dp++ = '\0';
  if( file.Read( data, len ) == wxInvalidOffset ) {
    delete[] data;
    data = nullptr;
    len = 0;
  }
  Ready();
}

void URLLoader::operator()() {
  wxURL url( fileName );
  url.SetProxy( ConfigManager::GetProxy() );
  if( url.GetError() != wxURL_NOERR ) {
    Ready();
    return;
  }
  std::unique_ptr<wxInputStream> stream( url.GetInputStream() );
  if( stream.get() == nullptr || stream->IsOk() == false ) {
    Ready();
    return;
  }
  char   tmp[8192] = {};
  size_t chunk     = 0;
  while( ( chunk = stream->Read( tmp, sizeof( tmp ) ).LastRead() ) ) {
    mBuffer.insert( mBuffer.end(), tmp, tmp + chunk );
  }
  #ifdef __APPLE__
  data = &mBuffer[0];
  #else
  data = mBuffer.data();
  #endif
  len  = mBuffer.size();
  const char Zeros4[] = "\0\0\0\0";
  mBuffer.insert( mBuffer.end(), Zeros4, Zeros4 + 4 );
  Ready();
}

FileManager::FileManager()
  : fileLoaderThread( false ),
    uncLoaderThread( false ),
    urlLoaderThread( false ) {
}

FileManager::~FileManager() {
}

LoaderBase* FileManager::Load( const wxString& file, bool reuseEditors ) {
  if( reuseEditors ) {
    EditorManager* em = Manager::Get()->GetEditorManager();
    if( em ) {
      wxFileName fileName( file );
      for( int i = 0; i < em->GetEditorsCount(); ++i ) {
        M_Code_Editor* ed = em->GetBuiltinEditor( em->GetEditor( i ) );
        if( ed && fileName == ed->GetFilename() ) {
          if( !ed->GetModified() ) {
            break;
          }
          EditorReuser *nl = new EditorReuser( file, ed->GetControl()->GetText() );
          return nl;
        }
      }
    }
  }
  if( file.StartsWith( _T( "http://" ) ) ) {
    URLLoader *ul = new URLLoader( file );
    urlLoaderThread.Queue( ul );
    return ul;
  }
  FileLoader *fl = new FileLoader( file );
  if( file.length() > 2 && file[0] == _T( '\\' ) && file[1] == _T( '\\' ) ) {
    uncLoaderThread.Queue( fl );
    return fl;
  }
  fileLoaderThread.Queue( fl );
  return fl;
}

namespace platform {
  inline bool move( wxString const& old_name, wxString const& new_name ) {
    return ::wxRenameFile( old_name, new_name, true );
  };
}

bool FileManager::SaveUTF8( const wxString& name, const char* data, size_t len ) {
  if( wxFileExists( name ) == false ) {
    return wxFile( name, wxFile::write_excl ).Write( data, len ) == len;
  } else {
    if( !wxFile::Access( name, wxFile::write ) ) {
      return false;
    }
    wxString temp( name );
    temp.append( _T( ".temp" ) );
    wxStructStat buff;
    wxLstat( name, &buff );
    wxFile f;
    f.Create( temp, true, buff.st_mode );
    if( f.Write( data, len ) == len ) {
      f.Close();
      if( platform::move( temp, name ) ) {
        return true;
      } else {
        wxString failed( name );
        failed.append( _T( ".save-failed" ) );
        platform::move( temp, failed );
      }
    }
    return false;
  }
}

bool FileManager::Save( const wxString& name, const wxString& data, wxFontEncoding encoding, bool bom ) {
  if( wxFileExists( name ) == false ) {
    wxFile f( name, wxFile::write_excl );
    return WriteWxStringToFile( f, data, encoding, bom );
  } else {
    if( !wxFile::Access( name, wxFile::write ) ) {
      return false;
    }
    wxString temp( name );
    temp.append( _T( ".temp" ) );
    wxStructStat buff;
    wxLstat( name, &buff );
    wxFile f;
    f.Create( temp, true, buff.st_mode );
    if( WriteWxStringToFile( f, data, encoding, bom ) ) {
      f.Close();
      if( platform::move( temp, name ) ) {
        return true;
      } else {
        wxString failed( name );
        failed.append( _T( ".save-failed" ) );
        platform::move( temp, failed );
      }
    }
    return false;
  }
}

bool FileManager::WriteWxStringToFile( wxFile& f, const wxString& data, wxFontEncoding encoding, bool bom ) {
  const char* mark = nullptr;
  size_t mark_length = 0;
  if( bom ) {
    switch( encoding ) {
      case wxFONTENCODING_UTF8:
        mark = "\xEF\xBB\xBF";
        mark_length = 3;
        break;
      case wxFONTENCODING_UTF16BE:
        mark = "\xFE\xFF";
        mark_length = 2;
        break;
      case wxFONTENCODING_UTF16LE:
        mark = "\xFF\xFE";
        mark_length = 2;
        break;
      case wxFONTENCODING_UTF32BE:
        mark = "\x00\x00\xFE\xFF";
        mark_length = 4;
        break;
      case wxFONTENCODING_UTF32LE:
        mark = "\xFF\xFE\x00\x00";
        mark_length = 4;
        break;
      case wxFONTENCODING_SYSTEM:
      default:
        break;
    }
    if( f.Write( mark, mark_length ) != mark_length ) {
      return false;
    }
  }
  if( data.length() == 0 ) {
    return true;
  }
  size_t inlen = data.Len(), outlen = 0;
  wxCharBuffer mbBuff;
  if( encoding == wxFONTENCODING_UTF7 ) {
    wxMBConvUTF7 conv;
    mbBuff = conv.cWC2MB( data.c_str(), inlen, &outlen );
  } else if( encoding == wxFONTENCODING_UTF8 ) {
    wxMBConvUTF8 conv;
    mbBuff = conv.cWC2MB( data.c_str(), inlen, &outlen );
  } else if( encoding == wxFONTENCODING_UTF16BE ) {
    wxMBConvUTF16BE conv;
    mbBuff = conv.cWC2MB( data.c_str(), inlen, &outlen );
  } else if( encoding == wxFONTENCODING_UTF16LE ) {
    wxMBConvUTF16LE conv;
    mbBuff = conv.cWC2MB( data.c_str(), inlen, &outlen );
  } else if( encoding == wxFONTENCODING_UTF32BE ) {
    wxMBConvUTF32BE conv;
    mbBuff = conv.cWC2MB( data.c_str(), inlen, &outlen );
  } else if( encoding == wxFONTENCODING_UTF32LE ) {
    wxMBConvUTF32LE conv;
    mbBuff = conv.cWC2MB( data.c_str(), inlen, &outlen );
  } else {
    wxEncodingConverter conv;
    char* tmp = new char[2 * inlen];
    if( conv.Init( wxFONTENCODING_UNICODE, encoding ) && conv.Convert( data.wx_str(), tmp ) ) {
      mbBuff = tmp;
      outlen = strlen( mbBuff );
    } else {
      wxCSConv csconv( encoding );
      mbBuff = csconv.cWC2MB( data.c_str(), inlen, &outlen );
    }
    delete[] tmp;
  }
  if( outlen > 0 ) {
    return f.Write( mbBuff, outlen ) == outlen;
  }
  size_t size = 0;
  wxCSConv conv( encoding );
  wxCharBuffer buf = data.mb_str( conv );
  if( !buf || !( size = strlen( buf ) ) ) {
    buf = data.mb_str( wxConvUTF8 );
    if( !buf || !( size = strlen( buf ) ) ) {
      msging( _T( "无法保存该文件 因包含当前代码页中不能表示的字符 无法转换成 UTF-8.\n"
                  "请检查编码设置并再次尝试保存." ),
              _T( "失败" ), wxICON_WARNING | wxOK );
      return false;
    } else {
      InfoWindow::Tip( _T( "保存的文档包含选定编码中非法的字符.\n\n"
                           "文件的编码已更改为 UTF-8 防止数据丢失." ), _T( "编码异常" ), 5000 );
    }
  }
  return f.Write( buf, size ) == size;
}
